import os
import subprocess
import shutil
import tkinter
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from typing import List, Optional

import pyperclip

import Fun
from tool import is_sub_path, walk_file, copy_files_and_folders, compare_files_by_hash, copy_file


@dataclass
class PackTask:
    """打包任务数据类"""
    py_path: str
    pyd_path: str
    backup_py_path: str
    needs_compile: bool = True
    status: str = "pending"  # pending, compiling, success, failed
    error_msg: str = ""

class Pack:
    def __init__(self):
        # *由界面指定的参数
        self.project_dir = ''
        self.main_file_path = ''
        self.pyd_dir = ''
        self.recompile = False
        self.is_keep = False
        # *手动指定的参数
        # !生成的pyd文件默认会带上这个后缀，根据各人电脑系统和python版本不同，可能有变化
        self.pyd_original_suffix = '.cp38-win_amd64'
        # *运行过程中自动生成
        self.has_false = False
        self.max_workers = 3  # 最大并发数，可以根据电脑性能调整
        self._lock = threading.Lock()  # 用于线程安全的输出

    # 一般收到的界面的参数都需要先处理才能用
    def deal_params(self, uiData):
        # 以下为界面传来的参数提示
        """
        uiData = {
        'project_dir': '',
        'main_file_path': '',
        'pyd_dir': '',
        'recompile': False,
        }
        """
        self.project_dir = uiData.get('project_dir', '')
        self.main_file_path = uiData.get('main_file_path', '')
        # 如果没有输入pyd路径（即空字符串""），就默认是项目根目录
        self.pyd_dir = uiData.get('pyd_dir', '') if uiData.get('pyd_dir') else self.project_dir
        self.recompile = uiData.get('recompile', False)
        self.is_keep = uiData.get('is_keep', False)

    def table_to_full_paths(self, table):
        py_paths = [os.path.join(self.project_dir, row[1]) for row in table]
        pyd_paths = [os.path.join(self.project_dir, row[2]) for row in table]
        return py_paths, pyd_paths

    def py_full_paths_to_table(self, py_full_paths: list, start_index=1):
        index_column = [str(i + start_index) for i in range(len(py_full_paths))]
        pyd_list = self.py_full_paths_to_pyd_paths(py_full_paths)
        pyd_text = ['不存在' if item is None else os.path.relpath(item, self.project_dir) for item in pyd_list]
        py_paths = [os.path.relpath(item, self.project_dir) for item in py_full_paths]
        table = list(map(list, zip(index_column, py_paths, pyd_text)))
        return table

    # 获取项目内所有的py文件列表，并检查其对应的pyd是否已经存在，返回结果的二维列表，用于界面上表格内显示
    def get_table(self, uiData):
        self.deal_params(uiData)
        # *如果嫌找到的不相干py文件太多，可以用walk_file的not_in参数，过滤掉包含['__init__', '__main__']之类字符的py文件
        py_full_paths = walk_file(self.project_dir, ext='py', not_in=['__init__'], get_full_paths=True, deep=True)
        # 如果不用normpath，就无法比较路径，因为有的路径是\\有的是/组成
        main_path = os.path.normpath(self.main_file_path)
        # 一般项目的入口文件，以及pyd文件夹内的py文件，不需要变成pyd
        py_full_paths = [item for item in py_full_paths if not
                is_sub_path(item, self.pyd_dir)]#  or os.path.normpath(item) == main_path)]
        table = self.py_full_paths_to_table(py_full_paths)
        return table

    def py_full_path_to_pyd_full_path(self, py_full_path):
        """
        将 Python 文件的完整路径转换为对应的编译 C 扩展文件（pyd 文件）的完整路径。

        参数:
        - py_full_path (str): Python 文件的完整路径。

        返回值:
        - pyd_full_path (str): 对应的 pyd 文件的完整路径。
        """
        py_rel_path = os.path.relpath(py_full_path, self.project_dir)
        if self.is_keep:
            pyd_full_path = os.path.join(self.pyd_dir, py_rel_path + 'd')
        else:
            base_name = os.path.basename(py_full_path)
            pyd_full_path = os.path.join(self.pyd_dir, base_name + 'd')
        return pyd_full_path

    def copy_pyd_dir(self, pyd_paths_text, new_dir):
        pyd_paths = [path for path in pyd_paths_text if path != '不存在']
        done = []
        for pyd in pyd_paths:
            src = os.path.join(self.project_dir, pyd)
            if not os.path.exists(src):
                continue
            else:
                rel_path = os.path.relpath(src, self.pyd_dir)
                dst = os.path.join(new_dir, rel_path)
                res = copy_file(src, dst)
                if res is True:
                    done.append(pyd)
                elif isinstance(res, str):
                    output(res)
                else:
                    raise "复制文件返回值错误"
        return done

    def thread_safe_output(self, text):
        """线程安全的输出函数"""
        with self._lock:
            output(text)

    def create_pack_tasks(self, py_paths: List[str]) -> List[PackTask]:
        """创建打包任务列表，优化判断逻辑"""
        tasks = []
        for py_path in py_paths:
            pyd_path = self.py_full_path_to_pyd_full_path(py_path)
            backup_py_path = self.py_full_path_to_pyd_py_path(py_path)
            
            # 优化判断逻辑：先检查pyd是否存在
            needs_compile = False
            
            if self.recompile:
                # 强制重编译模式：只有在py文件内容不同时才编译
                if os.path.exists(backup_py_path):
                    needs_compile = not compare_files_by_hash(py_path, backup_py_path)
                else:
                    # 备份py不存在，直接编译
                    needs_compile = True
            else:
                # 正常模式：先检查pyd是否存在
                if not os.path.exists(pyd_path):
                    # pyd不存在，直接编译，不用对比py文件
                    needs_compile = True
                elif os.path.exists(backup_py_path):
                    # pyd存在且备份py也存在，对比py文件内容
                    needs_compile = not compare_files_by_hash(py_path, backup_py_path)
                else:
                    # pyd存在但备份py不存在，需要编译来同步备份
                    needs_compile = True
            
            task = PackTask(
                py_path=py_path,
                pyd_path=pyd_path,
                backup_py_path=backup_py_path,
                needs_compile=needs_compile
            )
            tasks.append(task)
        
        return tasks

    def backup_py_file_safe(self, task: PackTask) -> Optional[str]:
        """安全的备份py文件，返回临时备份路径或None"""
        try:
            # 创建备份目录
            backup_dir = os.path.dirname(task.backup_py_path)
            if not os.path.exists(backup_dir):
                os.makedirs(backup_dir)
            
            # 如果备份文件已存在，创建临时备份
            temp_backup = None
            if os.path.exists(task.backup_py_path):
                temp_backup = task.backup_py_path + ".temp_backup"
                shutil.copy2(task.backup_py_path, temp_backup)
                self.thread_safe_output(f"已创建临时备份: {os.path.basename(temp_backup)}")
            
            # 复制新的py文件到备份位置
            copy_file(task.py_path, task.backup_py_path)
            
            return temp_backup
        except Exception as e:
            self.thread_safe_output(f"备份文件失败: {e}")
            return None

    def restore_backup_if_failed(self, task: PackTask, temp_backup: Optional[str]):
        """编译失败时的回滚逻辑：删除新复制的py，恢复原备份"""
        try:
            if temp_backup and os.path.exists(temp_backup):
                # 有临时备份：恢复之前的备份文件
                shutil.move(temp_backup, task.backup_py_path)
                self.thread_safe_output(f"编译失败，已恢复之前的备份: {os.path.relpath(task.backup_py_path, self.project_dir)}")
            else:
                # 没有临时备份：删除新复制的py文件
                if os.path.exists(task.backup_py_path):
                    os.remove(task.backup_py_path)
                    self.thread_safe_output(f"编译失败，已删除新复制的py文件: {os.path.relpath(task.backup_py_path, self.project_dir)}")
        except Exception as e:
            self.thread_safe_output(f"回滚失败: {e}")

    def finalize_backup_if_success(self, task: PackTask, temp_backup: Optional[str]):
        """编译成功时的最终化操作：清理临时备份"""
        try:
            # 清理临时备份文件（新的py已经在backup_py_file_safe中复制了）
            if temp_backup and os.path.exists(temp_backup):
                os.remove(temp_backup)
                self.thread_safe_output(f"编译成功，已清理临时备份")
        except Exception as e:
            self.thread_safe_output(f"清理临时备份失败: {e}")

    def add_py_path(self, paths, start_index):
        new_table = self.py_full_paths_to_table(paths, start_index)
        return new_table

    def nuitka_pack_multithreaded(self, table, uiData):
        """多线程版本的打包方法"""
        self.deal_params(uiData)
        py_paths, _ = self.table_to_full_paths(table)
        
        # 创建打包任务
        tasks = self.create_pack_tasks(py_paths)
        
        # 过滤出需要编译的任务
        compile_tasks = [task for task in tasks if task.needs_compile]
        skip_tasks = [task for task in tasks if not task.needs_compile]
        
        # 输出跳过的任务
        for task in skip_tasks:
            pyd_rel_path = os.path.relpath(task.pyd_path, self.project_dir)
            self.thread_safe_output(f'已存在 {pyd_rel_path}，跳过此打包')
        
        if not compile_tasks:
            self.thread_safe_output('所有文件都已最新，无需重新打包')
            return
        
        self.thread_safe_output(f'开始多线程打包，共{len(compile_tasks)}个文件，最大并发数: {self.max_workers}')
        
        # 使用线程池进行并发打包
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            # 提交所有任务
            future_to_task = {executor.submit(self.nuitka_one_file_safe, task): task for task in compile_tasks}
            
            # 处理完成的任务
            completed_count = 0
            for future in as_completed(future_to_task):
                task = future_to_task[future]
                completed_count += 1
                
                try:
                    success = future.result()
                    if success:
                        task.status = "success"
                        py_rel_path = os.path.relpath(task.py_path, self.project_dir)
                        self.thread_safe_output(f'[{completed_count}/{len(compile_tasks)}] 打包 {py_rel_path} 成功')
                    else:
                        task.status = "failed"
                        self.has_false = True
                        py_rel_path = os.path.relpath(task.py_path, self.project_dir)
                        self.thread_safe_output(f'[{completed_count}/{len(compile_tasks)}] 打包 {py_rel_path} 失败')
                except Exception as e:
                    task.status = "failed"
                    task.error_msg = str(e)
                    self.has_false = True
                    py_rel_path = os.path.relpath(task.py_path, self.project_dir)
                    self.thread_safe_output(f'[{completed_count}/{len(compile_tasks)}] 打包 {py_rel_path} 异常: {e}')
        
        # 统计结果
        success_count = sum(1 for task in tasks if task.status == "success")
        failed_count = sum(1 for task in tasks if task.status == "failed")
        skip_count = len(skip_tasks)
        
        self.thread_safe_output(f'打包完成! 成功: {success_count}, 失败: {failed_count}, 跳过: {skip_count}')

    def nuitka_one_file_safe(self, task: PackTask) -> bool:
        """安全的单文件打包方法，带有备份和恢复机制"""
        temp_backup = None
        try:
            # 先做备份
            temp_backup = self.backup_py_file_safe(task)
            
            # 执行打包
            success = self.nuitka_one_file_core(task)
            
            if success:
                # 打包成功，最终化备份
                self.finalize_backup_if_success(task, temp_backup)
                return True
            else:
                # 打包失败，恢复备份
                self.restore_backup_if_failed(task, temp_backup)
                return False
                
        except Exception as e:
            # 发生异常，恢复备份
            self.restore_backup_if_failed(task, temp_backup)
            task.error_msg = str(e)
            return False

    def nuitka_one_file_core(self, task: PackTask) -> bool:
        """核心打包逻辑，不包含备份和恢复逻辑"""
        import time
        py_path = task.py_path
        pyd_path = task.pyd_path
        
        if not os.path.isfile(py_path):
            task.error_msg = f"源文件不存在: {py_path}"
            return False
            
        # 确保输出目录存在
        pyd_dir = os.path.dirname(pyd_path)
        if not os.path.exists(pyd_dir):
            os.makedirs(pyd_dir)
        
        pyd_original_file_path = pyd_path[:-4] + self.pyd_original_suffix + '.pyd'
        order = f'nuitka --module --output-dir={pyd_dir} {py_path}'
        
        # 记录编译开始
        py_rel_path = os.path.relpath(py_path, self.project_dir)
        self.thread_safe_output(f"  开始编译: {py_rel_path}")
        compile_start = time.time()
        
        # 执行编译命令
        res = self.run_order(order)
        compile_time = time.time() - compile_start
        
        if res:
            self.thread_safe_output(f"  编译完成: {py_rel_path}, 耗时: {compile_time:.1f}秒")
            return self._handle_pyd_rename(task, pyd_path, pyd_original_file_path)
        else:
            self.thread_safe_output(f"  编译失败: {py_rel_path}, 耗时: {compile_time:.1f}秒")
            task.error_msg = f"nuitka编译失败"
            return False
    
    def _handle_pyd_rename(self, task: PackTask, pyd_path: str, pyd_original_file_path: str) -> bool:
        """处理pyd文件重命名，带有重试机制"""
        import time
        max_retries = 5
        retry_delay = 0.5  # 秒
        
        for attempt in range(max_retries):
            try:
                # 检查原始文件是否存在
                if not os.path.exists(pyd_original_file_path):
                    task.error_msg = f"编译后未找到预期的pyd文件: {pyd_original_file_path}"
                    return False
                
                # 记录重命名过程
                py_rel_path = os.path.relpath(task.py_path, self.project_dir)
                self.thread_safe_output(f"  正在重命名: {py_rel_path} -> {os.path.basename(pyd_path)}")
                
                # 删除旧的pyd文件（如果存在）
                if os.path.isfile(pyd_path):
                    self._safe_delete_file(pyd_path, max_wait=2.0)
                
                # 重命名新生成的pyd文件
                os.rename(os.path.normpath(pyd_original_file_path), os.path.normpath(pyd_path))
                
                # 验证重命名成功
                if os.path.exists(pyd_path):
                    return True
                else:
                    task.error_msg = f"重命名后文件不存在: {pyd_path}"
                    return False
                    
            except PermissionError as e:
                self.thread_safe_output(f"  重命名失败（尝试 {attempt+1}/{max_retries}）: 文件被占用")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                    retry_delay *= 1.5  # 逐渐增加等待时间
                else:
                    task.error_msg = f"重命名pyd文件失败（文件被占用）: {e}"
                    return False
            except Exception as e:
                self.thread_safe_output(f"  重命名失败（尝试 {attempt+1}/{max_retries}）: {e}")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                    retry_delay *= 1.5
                else:
                    task.error_msg = f"重命名pyd文件失败: {e}"
                    return False
        
        return False
    
    def _safe_delete_file(self, file_path: str, max_wait: float = 2.0) -> bool:
        """安全删除文件，带有超时机制"""
        import time
        import threading
        
        def delete_file():
            try:
                if os.path.isfile(file_path):
                    os.remove(file_path)
                return True
            except Exception:
                return False
        
        # 使用线程超时机制
        result = [False]
        
        def delete_with_timeout():
            result[0] = delete_file()
        
        delete_thread = threading.Thread(target=delete_with_timeout)
        delete_thread.daemon = True
        delete_thread.start()
        delete_thread.join(timeout=max_wait)
        
        if delete_thread.is_alive():
            self.thread_safe_output(f"  删除文件超时: {os.path.basename(file_path)}")
            return False
        
        return result[0]

    def py_full_path_to_pyd_py_path(self, py_full_path):
        """获取pyd目录中对应的py文件路径（不带'd'后缀）"""
        pyd_path = self.py_full_path_to_pyd_full_path(py_full_path)
        return pyd_path[:-1]  # 去掉最后的'd'，即从.pyd变成.py

    def nuitka_pack(self, table, uiData):
        # !注意:①项目路径中不要包含中文字符②即使是不同路径也不要包含同名的py文件，否则pyd文件也会重名
        self.deal_params(uiData)
        py_paths, _ = self.table_to_full_paths(table)
        pack_py_paths = self.copy_py_backup_and_get_pack_py_path(py_paths)
        output('开始对比 表格中需要打包的文件 和 pyd目录中备份的文件 ，以确定哪些py文件需要打包')
        self.has_false = False
        for py_path in pack_py_paths:
            if not os.path.isfile(py_path):
                output(py_path)
                continue
            self.nuitka_one_file(py_path)

    def create_order(self, py_paths):
        pyinstaller_order = []
        for py_path in py_paths:
            py_path = os.path.join(self.project_dir, py_path)
            pyd_path = self.py_full_path_to_pyd_full_path(py_path)
            pyd_rel_path = os.path.relpath(pyd_path, self.project_dir)
            py_rel_path = os.path.relpath(os.path.dirname(py_path), self.project_dir)
            main_name = os.path.basename(py_path)[:-3]

            pyinstaller_order.append(f'--add-binary={pyd_rel_path}:{py_rel_path}')
            pyinstaller_order.append(f'--exclude-module={main_name}')
        return " ".join(pyinstaller_order)


    def nuitka_one_file(self, py_path):
        if not os.path.isfile(py_path):
            output(py_path)
        pyd_path = self.py_full_path_to_pyd_full_path(py_path)
        pyd_original_file_path = pyd_path[:-4] + self.pyd_original_suffix + '.pyd'
        order = f'nuitka --module --output-dir={os.path.dirname(pyd_path)} {py_path}'
        output(f'开始打包 {py_path}...')
        res = self.run_order(order)
        if res is True:
            # 防止重名文件，先删除
            if os.path.isfile(pyd_path):
                self.delete(pyd_path)
            if os.path.isfile(pyd_path):
                self.delete(pyd_path)
            try:
                # 将初始的pyd文件重命名
                os.rename(os.path.normpath(pyd_original_file_path), os.path.normpath(pyd_path))
                output(f'打包 {py_path}成功')
            except FileExistsError:
                output(f'打包{py_path}时检测到已存在相应pyd文件')
            except Exception as e:
                self.has_false = True
                output(f'打包{py_path}时出现未知错误：{e}')
        else:
            self.has_false = True
            output(f'打包 {py_path}失败')

    # def nuitka_pack(self, table, uiData):
    #     # !注意:①项目路径中不要包含中文字符②即使是不同路径也不要包含同名的py文件，否则pyd文件也会重名
    #     self.deal_params(uiData)
    #     py_paths, _ = self.table_to_full_paths(table)
    #     pack_py_paths = self.copy_py_backup_and_get_pack_py_path(py_paths)
    #
    #     pyinstaller_order_list = []
    #     py_text = []
    #     for py_path in pack_py_paths:
    #         if not os.path.isfile(py_path):
    #             output(py_path)
    #             continue
    #         pyd_path = self.py_full_path_to_pyd_full_path(py_path)
    #         py_base_name = os.path.basename(py_path)
    #         py_rel_path = os.path.relpath(py_path, self.project_dir)
    #         # 打包出的pyd初始名称会带后缀，此处记录初始名称和要重命名的名称，用于后面重命名
    #         pyd_original_file_path = pyd_path[-4] + self.pyd_original_suffix + '.pyd'
    #         pyd_rel_path = os.path.relpath(pyd_path, self.project_dir)
    #         pyd_dir_name = os.path.dirname(pyd_path)
    #         # 要么重编绎为真，要么pyd不存在，那么就需要编绎。否则不需要再编绎了
    #         if self.recompile or not os.path.isfile(pyd_path):
    #             order = f'nuitka --module --output-dir={pyd_path} {py_path}'
    #             output(f'开始打包 {py_path}...')
    #             res = self.run_order(order)
    #             if res is True:
    #                 # 防止重名文件，先删除
    #                 if os.path.isfile(pyd_file_path):
    #                     os.remove(pyd_file_path)
    #                 try:
    #                     # 将初始的pyd文件重命名
    #                     os.rename(os.path.normpath(pyd_original_file_path), os.path.normpath(pyd_file_path))
    #                     # 更新显示的表格的信息
    #                     table[i][2] = os.path.relpath(pyd_file_path, self.project_dir)
    #                     # 重新编绎后，将pyd加入命令
    #                     pyinstaller_order_list.append(f'--add-binary={pyd_rel_path}:{py_rel_path}')
    #                     pyinstaller_order_list.append(f'--exclude-module={py_base_name[:-3]}')
    #                     output(f'打包 {py_path}成功')
    #                 except FileExistsError:
    #                     table[i][2] = os.path.relpath(pyd_file_path, self.project_dir)
    #                     pyinstaller_order_list.append(f'--add-binary={pyd_rel_path}:{py_rel_path}')
    #                     pyinstaller_order_list.append(f'--exclude-module={py_base_name[:-3]}')
    #                     output(f'打包{py_path}时检测到已存在相应pyd文件')
    #                 except Exception as e:
    #                     output(f'打包{py_path}时出现未知错误：{e}')
    #                     table[i][2] = '打包失败'
    #             else:
    #                 output(f'打包 {py_path}失败')
    #                 # 更新显示的表格的信息
    #                 table[i][2] = '打包失败'
    #         else:
    #             # 即使不重新编绎，但仍有相应的pyd，直接加入命令
    #             pyinstaller_order_list.append(f'--add-binary={pyd_rel_path}:{py_rel_path}')
    #             pyinstaller_order_list.append(f'--exclude-module={py_base_name[:-3]}')
    #             output(f'已存在 {pyd_rel_path}，跳过此打包')
    #     pyinstaller_order = ' '.join(pyinstaller_order_list)
    #     return table, pyinstaller_order

    @staticmethod
    def run_order(order):
        import time
        start_time = time.time()

        # 记录开始执行
        file_name = order.split()[-1] if ' ' in order else "unknown"
        base_name = os.path.basename(file_name) if file_name != "unknown" else "unknown"

        try:
            # 使用utf-8编码来避免gbk解码错误
            result = subprocess.run(
                order,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8',
                errors='ignore'  # 忽略编码错误
            )
            end_time = time.time()
            duration = end_time - start_time

            # 记录执行结果
            success = result.returncode == 0
            status = "成功" if success else "失败"

            # 如果编译耗时超过阈值，显示详细信息
            if duration > 5.0:  # 5秒以上显示详细信息
                output(f"  Nuitka编译{status}: {base_name}, 耗时: {duration:.1f}秒")

                # 如果失败，显示错误信息
                if not success and result.stderr:
                    # 清理可能的编码问题
                    stderr_clean = result.stderr.encode('utf-8', errors='ignore').decode('utf-8', errors='ignore')
                    output(f"  错误信息: {stderr_clean[:200]}...")

            return success

        except Exception as e:
            end_time = time.time()
            duration = end_time - start_time
            output(f"  Nuitka执行异常: {base_name}, 耗时: {duration:.1f}秒, 错误: {e}")
            return False

    def clean_artifacts(self, data):  # Todo
        self.deal_params(data)
        if not os.path.isdir(self.pyd_dir):
            output('清理失败，pyd目录设置错误')
            return
        artifacts = walk_file(self.pyd_dir, ext='pyi')
        for root, dirs, files in os.walk(self.pyd_dir):
            # 过滤出名字以 .build 结尾的文件夹
            for dir_name in dirs:
                if dir_name.endswith('.build'):
                    full_path = os.path.join(root, dir_name)
                    artifacts.append(full_path)
        if not artifacts:
            output(f'在{self.pyd_dir}内未找到可清理的文件')
            return
        for path in artifacts:
            res = self.delete(path)
            if res:
                output(f'清理 {path}成功')
            else:
                output(f'清理 {path}失败，错误为{res}')

    def copy_py_backup_and_get_pack_py_path(self, py_paths):
        """
        复制Python文件作为备份，并获取打包后的Python文件路径。

        此函数根据是否需要重新编译，决定是否对给定的Python文件路径列表进行处理。
        如果不需要重新编译且对应的编译文件已存在，则不进行任何操作。
        如果需要重新编译，或者对应的编译文件不存在，则会将原始Python文件复制到新的位置。

        参数:
        py_paths (list): Python文件的路径列表。

        返回:
        list: 打包后的Python文件路径列表。如果不需要进行操作，则返回空字符串。
        """
        # 初始化打包的Python文件路径列表
        pack_py_path = []

        # 将完整的Python文件路径转换为对应的编译文件路径
        pyd_paths = self.py_full_paths_to_pyd_paths(py_paths)

        # 将完整的Python文件路径转换为打包后的Python文件路径
        pyd_py_paths = self.py_full_paths_to_pyd_py_paths(py_paths)

        # 遍历原始Python文件路径、新的Python文件路径和编译文件路径
        for py_path_old, py_path_new, pyd_path in zip(pyd_py_paths, py_paths, pyd_paths):
            if not pyd_path:
                pyd_path = self.py_full_path_to_pyd_full_path(py_path_new)
            # 如果不需要重新编译
            if not self.recompile:
                # 如果编译文件存在，则不进行操作
                if os.path.exists(pyd_path):
                    # copy_file(py_path_new, py_path_old)
                    pack_py_path.append(f'{os.path.relpath(pyd_path, self.project_dir)} 此文件已存在，不需要重新编绎')
                else:
                    # 复制原始Python文件到新的位置，并添加到结果列表中
                    copy_file(py_path_new, py_path_old)
                    pack_py_path.append(py_path_new)
            else:
                # 如果原始Python文件和新的Python文件内容相同，则不进行操作
                if compare_files_by_hash(py_path_old, py_path_new):
                    pack_py_path.append(
                        f'{os.path.relpath(py_path_new, self.project_dir)} 与pyd文件夹内的备份py文件内容相同，不需要重新编绎')
                else:
                    # 复制原始Python文件到新的位置，并添加到结果列表中
                    copy_file(py_path_new, py_path_old)
                    pack_py_path.append(py_path_new)
        return pack_py_path

    @staticmethod
    def delete(path):
        try:
            if os.path.isfile(path):
                os.remove(path)
            elif os.path.isdir(path):
                shutil.rmtree(path)
            else:
                return "未找到文件"
        except Exception as e:
            return e
        return True

    def py_full_paths_to_pyd_paths(self, py_full_paths: list):
        """
        将Python文件的完整路径列表转换为对应的编译动态链接库（pyd文件）的路径列表。

        该函数遍历Python文件的完整路径列表，将每个路径转换为对应pyd文件的相对路径，前提是pyd文件存在。
        如果pyd文件不存在，则返回None。

        参数:
        - py_full_paths (list): 包含Python文件完整路径的列表。

        返回:
        - list: 包含对应pyd文件的相对路径的列表，如果pyd文件不存在则返回None。
        """
        return [
            self.py_full_path_to_pyd_full_path(full_path)
            if os.path.isfile(self.py_full_path_to_pyd_full_path(full_path))
            else None
            for full_path in py_full_paths
        ]

    def py_full_paths_to_pyd_py_paths(self, py_full_paths):
        pyd_py_paths = []
        for py_full_path in py_full_paths:
            py_pyd_full_path = self.py_full_path_to_pyd_full_path(py_full_path)[:-1]
            pyd_py_paths.append(py_pyd_full_path)
        return pyd_py_paths

    @staticmethod
    def check_module_names(py_paths):
        modules = [os.path.basename(path)[:-3] for path in py_paths]
        if len(set(modules)) != len(py_paths):
            return False
        else:
            return True


def output(text):
    Fun.InsertText('AutoPack', 'info_output', tkinter.END, f'{text}\n\n', '')



def get_pyd(dir_name):
    """--add-binary=pydFiles\ExcelTool_sty.pyd:. --exclude-module=ExcelTool_sty"""
    files = walk_file(dir_name, 'pyd')
    order = []
    for file in files:
        relpath = os.path.relpath(file, dir_name)
        reldir = os.path.dirname(relpath) or '.'
        module_name = os.path.basename(file)[:-4]
        order.append(f'--add-binary={relpath}:{reldir} --exclude-module={module_name}')
    order = ' '.join(order)
    return order


if __name__ == '__main__':
    order = get_pyd(r'D:\Project\PyStand-py38-x64-ExcelTool\app')
    pyperclip.copy(order)
    print(order)